/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
 */
//
/**********************************************************************
        UI_QMENU.C

        Quake's menu framework system.
 **********************************************************************/
#include "ui_local.h"

sfxHandle_t menu_in_sound;
sfxHandle_t menu_move_sound;
sfxHandle_t menu_out_sound;
sfxHandle_t menu_buzz_sound;
sfxHandle_t menu_null_sound;
sfxHandle_t weaponChangeSound;

static qhandle_t sliderBar;
static qhandle_t sliderButton_0;
static qhandle_t sliderButton_1;

vec4_t menu_text_color = {1.0f, 1.0f, 1.0f, 1.0f};
vec4_t menu_dim_color = {0.0f, 0.0f, 0.0f, 0.75f};
vec4_t color_black = {0.00f, 0.00f, 0.00f, 1.00f};
vec4_t color_white = {1.00f, 1.00f, 1.00f, 1.00f};
vec4_t color_yellow = {1.00f, 1.00f, 0.00f, 1.00f};
vec4_t color_blue = {0.00f, 0.00f, 1.00f, 1.00f};
vec4_t color_lightOrange = {1.00f, 0.68f, 0.00f, 1.00f};
vec4_t color_orange = {1.00f, 0.43f, 0.00f, 1.00f};
vec4_t color_red = {1.00f, 0.00f, 0.00f, 1.00f};
vec4_t color_dim = {0.00f, 0.00f, 0.00f, 0.25f};
vec4_t color_green = {0.00f, 1.00f, 0.00f, 1.00f};

// current color scheme
vec4_t pulse_color = {1.00f, 1.00f, 1.00f, 1.00f};
vec4_t text_color_disabled = {0.50f, 0.50f, 0.50f, 1.00f}; // light gray
vec4_t text_color_normal = {1.00f, 0.43f, 0.00f, 1.00f}; // light orange
vec4_t text_color_highlight = {1.00f, 1.00f, 0.00f, 1.00f}; // bright yellow
vec4_t listbar_color = {1.00f, 0.43f, 0.00f, 0.30f}; // transluscent orange
vec4_t text_color_status = {1.00f, 1.00f, 1.00f, 1.00f}; // bright white

// action widget
static void Action_Init(menuaction_s *a);
static void Action_Draw(menuaction_s *a);

// radio button widget
static void RadioButton_Init(menuradiobutton_s *rb);
static void RadioButton_Draw(menuradiobutton_s *rb);
static sfxHandle_t RadioButton_Key(menuradiobutton_s *rb, int key);

// slider widget
static void Slider_Init(menuslider_s *s);
static sfxHandle_t Slider_Key(menuslider_s *s, int key);
static void Slider_Draw(menuslider_s *s);

// spin control widget
static void SpinControl_Init(menulist_s *s);
static void SpinControl_Draw(menulist_s *s);
static sfxHandle_t SpinControl_Key(menulist_s *l, int key);

// text widget
static void Text_Init(menutext_s *b);
static void Text_Draw(menutext_s *b);

// scrolllist widget
static void ScrollList_Init(menulist_s *l);
sfxHandle_t ScrollList_Key(menulist_s *l, int key);

// proportional text widget
static void PText_Init(menutext_s *b);
static void PText_Draw(menutext_s *b);

// proportional banner text widget
static void BText_Init(menutext_s *b);
static void BText_Draw(menutext_s *b);

/*
=================
Text_Init
=================
 */
static void Text_Init(menutext_s *t) {
    t->generic.flags |= QMF_INACTIVE;
}

/*
=================
Text_Draw
=================
 */
static void Text_Draw(menutext_s *t) {
    int x;
    int y;
    char buff[512];
    float* color;

    x = t->generic.x;
    y = t->generic.y;

    buff[0] = '\0';

    // possible label
    if (t->generic.name)
        strcpy(buff, t->generic.name);

    // possible value
    if (t->string)
        strcat(buff, t->string);

    if (t->generic.flags & QMF_GRAYED)
        color = text_color_disabled;
    else
        color = t->color;

    UI_DrawString(x, y, buff, t->style, color);
}

/*
=================
BText_Init
=================
 */
static void BText_Init(menutext_s *t) {
    t->generic.flags |= QMF_INACTIVE;
}

/*
=================
BText_Draw
=================
 */
static void BText_Draw(menutext_s *t) {
    int x;
    int y;
    float* color;

    x = t->generic.x;
    y = t->generic.y;

    if (t->generic.flags & QMF_GRAYED)
        color = text_color_disabled;
    else
        color = t->color;

    UI_DrawString(x, y, t->string, t->style, color);
    //UI_DrawBannerString(x, y, t->string, t->style, color);
}

/*
=================
PText_Init
=================
 */
static void PText_Init(menutext_s *t) {
    int x;
    int y;
    int w;
    int h;
    float sizeScale;

    sizeScale = UI_ProportionalSizeScale(t->style);

    x = t->generic.x;
    y = t->generic.y;
    w = UI_ProportionalStringWidth(t->string) * sizeScale;
    h = PROP_HEIGHT * sizeScale;

    if (t->generic.flags & QMF_RIGHT_JUSTIFY) {
        x -= w;
    } else if (t->generic.flags & QMF_CENTER_JUSTIFY) {
        x -= w / 2;
    }

    t->generic.left = x - PROP_GAP_WIDTH * sizeScale;
    t->generic.right = x + w + PROP_GAP_WIDTH * sizeScale;
    t->generic.top = y;
    t->generic.bottom = y + h;
}

/*
=================
PText_Draw
=================
 */
static void PText_Draw(menutext_s *t) {
    int x;
    int y;
    float * color;
    int style;

    x = t->generic.x;
    y = t->generic.y;

    if (t->generic.flags & QMF_GRAYED)
        color = text_color_disabled;
    else
        color = t->color;

    style = t->style;
    if (t->generic.flags & QMF_PULSEIFFOCUS) {
        if (Menu_ItemAtCursor(t->generic.parent) == t) {
            style |= UI_PULSE;
        } else {
            style |= UI_INVERSE;
        }
    }

    UI_DrawString(x, y, t->string, t->style, color);
    //UI_DrawProportionalString(x, y, t->string, style, color);
}

/*
=================
Bitmap_Init
=================
 */
void Bitmap_Init(menubitmap_s *b) {
    int x;
    int y;
    int w;
    int h;

    x = b->generic.x;
    y = b->generic.y;
    w = b->width;
    h = b->height;
    if (w < 0) {
        w = -w;
    }
    if (h < 0) {
        h = -h;
    }

    if (b->generic.flags & QMF_RIGHT_JUSTIFY) {
        x = x - w;
    } else if (b->generic.flags & QMF_CENTER_JUSTIFY) {
        x = x - w / 2;
    }

    b->generic.left = x;
    b->generic.right = x + w;
    b->generic.top = y;
    b->generic.bottom = y + h;

    b->shader = 0;
    b->focusshader = 0;
}

/*
=================
Bitmap_Draw
=================
 */
void Bitmap_Draw(menubitmap_s *b) {
    float x;
    float y;
    float w;
    float h;
    vec4_t tempcolor;
    float* color;

    x = b->generic.x;
    y = b->generic.y;
    w = b->width;
    h = b->height;

    if (b->generic.flags & QMF_RIGHT_JUSTIFY) {
        x = x - w;
    } else if (b->generic.flags & QMF_CENTER_JUSTIFY) {
        x = x - w / 2;
    }

    // used to refresh shader
    if (b->generic.name && !b->shader) {
        b->shader = trap_R_RegisterShaderNoMip(b->generic.name);
        if (!b->shader && b->errorpic)
            b->shader = trap_R_RegisterShaderNoMip(b->errorpic);
    }

    if (b->focuspic && !b->focusshader)
        b->focusshader = trap_R_RegisterShaderNoMip(b->focuspic);

    if (b->generic.flags & QMF_GRAYED) {
        if (b->shader) {
            trap_R_SetColor(colorMdGrey);
            UI_DrawHandlePic(x, y, w, h, b->shader);
            trap_R_SetColor(NULL);
        }
    } else {
        if (b->shader)
            UI_DrawHandlePic(x, y, w, h, b->shader);

        if (((b->generic.flags & QMF_PULSE)
                || (b->generic.flags & QMF_PULSEIFFOCUS))
                && (Menu_ItemAtCursor(b->generic.parent) == b)) {
            if (b->focuscolor) {
                tempcolor[0] = b->focuscolor[0];
                tempcolor[1] = b->focuscolor[1];
                tempcolor[2] = b->focuscolor[2];
                color = tempcolor;
            } else
                color = pulse_color;
            color[3] = 0.5 + 0.5 * sin(uis.realtime / PULSE_DIVISOR);

            trap_R_SetColor(color);
            UI_DrawHandlePic(x, y, w, h, b->focusshader);
            trap_R_SetColor(NULL);
        } else if ((b->generic.flags & QMF_HIGHLIGHT) || ((b->generic.flags & QMF_HIGHLIGHT_IF_FOCUS) && (Menu_ItemAtCursor(b->generic.parent) == b))) {
            if (b->focuscolor) {
                trap_R_SetColor(b->focuscolor);
                UI_DrawHandlePic(x, y, w, h, b->focusshader);
                trap_R_SetColor(NULL);
            } else
                UI_DrawHandlePic(x, y, w, h, b->focusshader);
        }
    }
}

/*
=================
Action_Init
=================
 */
static void Action_Init(menuaction_s *a) {
    int len;

    // calculate bounds
    if (a->generic.name)
        len = strlen(a->generic.name);
    else
        len = 0;

    // left justify text
    a->generic.left = a->generic.x;
    a->generic.right = a->generic.x + len*BIGCHAR_WIDTH;
    a->generic.top = a->generic.y;
    a->generic.bottom = a->generic.y + BIGCHAR_HEIGHT;
}

/*
=================
Action_Draw
=================
 */
static void Action_Draw(menuaction_s *a) {
    int x, y;
    int style;
    float* color;

    style = 0;
    color = menu_text_color;
    if (a->generic.flags & QMF_GRAYED) {
        color = text_color_disabled;
    } else if ((a->generic.flags & QMF_PULSEIFFOCUS) && (a->generic.parent->cursor == a->generic.menuPosition)) {
        color = text_color_highlight;
        style = UI_PULSE;
    } else if ((a->generic.flags & QMF_HIGHLIGHT_IF_FOCUS) && (a->generic.parent->cursor == a->generic.menuPosition)) {
        color = text_color_highlight;
    } else if (a->generic.flags & QMF_BLINK) {
        style = UI_BLINK;
        color = text_color_highlight;
    }

    x = a->generic.x;
    y = a->generic.y;

    UI_DrawString(x, y, a->generic.name, UI_LEFT | style, color);

    if (a->generic.parent->cursor == a->generic.menuPosition) {
        // draw cursor
        UI_DrawChar(x - BIGCHAR_WIDTH, y, 13, UI_LEFT | UI_BLINK, color);
    }
}

/*
=================
RadioButton_Init
=================
 */
static void RadioButton_Init(menuradiobutton_s *rb) {
    int len;

    // calculate bounds
    if (rb->generic.name)
        len = strlen(rb->generic.name);
    else
        len = 0;

    rb->generic.left = rb->generic.x - (len + 1) * SMALLCHAR_WIDTH;
    rb->generic.right = rb->generic.x + 6 * SMALLCHAR_WIDTH;
    rb->generic.top = rb->generic.y;
    rb->generic.bottom = rb->generic.y + SMALLCHAR_HEIGHT;
}

/*
=================
RadioButton_Key
=================
 */
static sfxHandle_t RadioButton_Key(menuradiobutton_s *rb, int key) {
    switch (key) {
        case K_MOUSE1:
            if (!(rb->generic.flags & QMF_HASMOUSEFOCUS))
                break;

        case K_JOY1:
        case K_JOY2:
        case K_JOY3:
        case K_JOY4:
        case K_ENTER:
        case K_KP_ENTER:
        case K_KP_LEFTARROW:
        case K_LEFTARROW:
        case K_KP_RIGHTARROW:
        case K_RIGHTARROW:
            rb->curvalue = !rb->curvalue;
            if (rb->generic.callback)
                rb->generic.callback(rb, QM_ACTIVATED);

            return (menu_move_sound);
    }

    // key not handled
    return 0;
}

/*
=================
RadioButton_Draw
=================
 */
static void RadioButton_Draw(menuradiobutton_s *rb) {
    int x;
    int y;
    float *color;
    int style;
    qboolean focus;

    x = rb->generic.x;
    y = rb->generic.y;

    focus = (rb->generic.parent->cursor == rb->generic.menuPosition);

    if (rb->generic.flags & QMF_GRAYED) {
        color = text_color_disabled;
        style = UI_LEFT | UI_SMALLFONT;
    } else if (focus) {
        color = text_color_highlight;
        style = UI_LEFT | UI_PULSE | UI_SMALLFONT;
    } else {
        color = text_color_normal;
        style = UI_LEFT | UI_SMALLFONT;
    }

    if (focus) {
        // draw cursor
        UI_FillRect(rb->generic.left, rb->generic.top, rb->generic.right - rb->generic.left + 1, rb->generic.bottom - rb->generic.top + 1, listbar_color);
        UI_DrawChar(x, y, 13, UI_CENTER | UI_BLINK | UI_SMALLFONT, color);
    }

    if (rb->generic.name)
        UI_DrawString(x - SMALLCHAR_WIDTH, y, rb->generic.name, UI_RIGHT | UI_SMALLFONT, color);

    if (!rb->curvalue) {
        UI_DrawHandlePic(x + SMALLCHAR_WIDTH, y + 2, 16, 16, uis.rb_off);
        UI_DrawString(x + SMALLCHAR_WIDTH + 16, y, "off", style, color);
    } else {
        UI_DrawHandlePic(x + SMALLCHAR_WIDTH, y + 2, 16, 16, uis.rb_on);
        UI_DrawString(x + SMALLCHAR_WIDTH + 16, y, "on", style, color);
    }
}

/*
=================
Slider_Init
=================
 */
static void Slider_Init(menuslider_s *s) {
    int len;

    // calculate bounds
    if (s->generic.name)
        len = strlen(s->generic.name);
    else
        len = 0;

    s->generic.left = s->generic.x - (len + 1) * SMALLCHAR_WIDTH;
    s->generic.right = s->generic.x + (SLIDER_RANGE + 2 + 1) * SMALLCHAR_WIDTH;
    s->generic.top = s->generic.y;
    s->generic.bottom = s->generic.y + SMALLCHAR_HEIGHT;
}

/*
=================
Slider_Key
=================
 */
static sfxHandle_t Slider_Key(menuslider_s *s, int key) {
    sfxHandle_t sound;
    int x;
    int oldvalue;

    switch (key) {
        case K_MOUSE1:
            x = uis.cursorx - s->generic.x - 2 * SMALLCHAR_WIDTH;
            oldvalue = s->curvalue;
            s->curvalue = (x / (float) (SLIDER_RANGE * SMALLCHAR_WIDTH)) * (s->maxvalue - s->minvalue) + s->minvalue;

            if (s->curvalue < s->minvalue)
                s->curvalue = s->minvalue;
            else if (s->curvalue > s->maxvalue)
                s->curvalue = s->maxvalue;
            if (s->curvalue != oldvalue)
                sound = menu_move_sound;
            else
                sound = 0;
            break;

        case K_KP_LEFTARROW:
        case K_LEFTARROW:
            if (s->curvalue > s->minvalue) {
                s->curvalue--;
                sound = menu_move_sound;
            } else
                sound = menu_buzz_sound;
            break;

        case K_KP_RIGHTARROW:
        case K_RIGHTARROW:
            if (s->curvalue < s->maxvalue) {
                s->curvalue++;
                sound = menu_move_sound;
            } else
                sound = menu_buzz_sound;
            break;

        default:
            // key not handled
            sound = 0;
            break;
    }

    if (sound && s->generic.callback)
        s->generic.callback(s, QM_ACTIVATED);

    return (sound);
}

#if 1

/*
=================
Slider_Draw
=================
 */
static void Slider_Draw(menuslider_s *s) {
    int x;
    int y;
    int style;
    float *color;
    int button;
    qboolean focus;

    x = s->generic.x;
    y = s->generic.y;
    focus = (s->generic.parent->cursor == s->generic.menuPosition);

    if (s->generic.flags & QMF_GRAYED) {
        color = text_color_disabled;
        style = UI_SMALLFONT;
    } else if (focus) {
        color = text_color_highlight;
        style = UI_SMALLFONT | UI_PULSE;
    } else {
        color = text_color_normal;
        style = UI_SMALLFONT;
    }

    // draw label
    UI_DrawString(x - SMALLCHAR_WIDTH, y, s->generic.name, UI_RIGHT | style, color);

    // draw slider
    UI_SetColor(color);
    UI_DrawHandlePic(x + SMALLCHAR_WIDTH, y, 96, 16, sliderBar);
    UI_SetColor(NULL);

    // clamp thumb
    if (s->maxvalue > s->minvalue) {
        s->range = (s->curvalue - s->minvalue) / (float) (s->maxvalue - s->minvalue);
        if (s->range < 0) {
            s->range = 0;
        } else if (s->range > 1) {
            s->range = 1;
        }
    } else {
        s->range = 0;
    }

    // draw thumb
    if (style & UI_PULSE) {
        button = sliderButton_1;
    } else {
        button = sliderButton_0;
    }

    UI_DrawHandlePic((int) (x + 2 * SMALLCHAR_WIDTH + (SLIDER_RANGE - 1) * SMALLCHAR_WIDTH * s->range) - 2, y - 2, 12, 20, button);
}
#else

/*
=================
Slider_Draw
=================
 */
static void Slider_Draw(menuslider_s *s) {
    float *color;
    int style;
    int i;
    int x;
    int y;
    qboolean focus;

    x = s->generic.x;
    y = s->generic.y;
    focus = (s->generic.parent->cursor == s->generic.menuPosition);

    style = UI_SMALLFONT;
    if (s->generic.flags & QMF_GRAYED) {
        color = text_color_disabled;
    } else if (focus) {
        color = text_color_highlight;
        style |= UI_PULSE;
    } else {
        color = text_color_normal;
    }

    if (focus) {
        // draw cursor
        UI_FillRect(s->generic.left, s->generic.top, s->generic.right - s->generic.left + 1, s->generic.bottom - s->generic.top + 1, listbar_color);
        UI_DrawChar(x, y, 13, UI_CENTER | UI_BLINK | UI_SMALLFONT, color);
    }

    // draw label
    UI_DrawString(x - SMALLCHAR_WIDTH, y, s->generic.name, UI_RIGHT | style, color);

    // draw slider
    UI_DrawChar(x + SMALLCHAR_WIDTH, y, 128, UI_LEFT | style, color);
    for (i = 0; i < SLIDER_RANGE; i++)
        UI_DrawChar(x + (i + 2) * SMALLCHAR_WIDTH, y, 129, UI_LEFT | style, color);
    UI_DrawChar(x + (i + 2) * SMALLCHAR_WIDTH, y, 130, UI_LEFT | style, color);

    // clamp thumb
    if (s->maxvalue > s->minvalue) {
        s->range = (s->curvalue - s->minvalue) / (float) (s->maxvalue - s->minvalue);
        if (s->range < 0)
            s->range = 0;
        else if (s->range > 1)
            s->range = 1;
    } else
        s->range = 0;

    // draw thumb
    if (style & UI_PULSE) {
        style &= ~UI_PULSE;
        style |= UI_BLINK;
    }
    UI_DrawChar((int) (x + 2 * SMALLCHAR_WIDTH + (SLIDER_RANGE - 1) * SMALLCHAR_WIDTH * s->range), y, 131, UI_LEFT | style, color);
}
#endif

/*
=================
SpinControl_Init
=================
 */
static void SpinControl_Init(menulist_s *s) {
    int len;
    int l;
    const char* str;

    if (s->generic.name)
        len = strlen(s->generic.name) * SMALLCHAR_WIDTH;
    else
        len = 0;

    s->generic.left = s->generic.x - SMALLCHAR_WIDTH - len;

    len = s->numitems = 0;
    while ((str = s->itemnames[s->numitems]) != 0) {
        l = strlen(str);
        if (l > len)
            len = l;

        s->numitems++;
    }

    s->generic.top = s->generic.y;
    s->generic.right = s->generic.x + (len + 1) * SMALLCHAR_WIDTH;
    s->generic.bottom = s->generic.y + SMALLCHAR_HEIGHT;
}

/*
=================
SpinControl_Key
=================
 */
static sfxHandle_t SpinControl_Key(menulist_s *s, int key) {
    sfxHandle_t sound;

    sound = 0;
    switch (key) {
        case K_MOUSE1:
            s->curvalue++;
            if (s->curvalue >= s->numitems)
                s->curvalue = 0;
            sound = menu_move_sound;
            break;

        case K_KP_LEFTARROW:
        case K_LEFTARROW:
            if (s->curvalue > 0) {
                s->curvalue--;
                sound = menu_move_sound;
            } else
                sound = menu_buzz_sound;
            break;

        case K_KP_RIGHTARROW:
        case K_RIGHTARROW:
            if (s->curvalue < s->numitems - 1) {
                s->curvalue++;
                sound = menu_move_sound;
            } else
                sound = menu_buzz_sound;
            break;
    }

    if (sound && s->generic.callback)
        s->generic.callback(s, QM_ACTIVATED);

    return (sound);
}

/*
=================
SpinControl_Draw
=================
 */
static void SpinControl_Draw(menulist_s *s) {
    float *color;
    int x, y;
    int style;
    qboolean focus;

    x = s->generic.x;
    y = s->generic.y;

    style = UI_SMALLFONT;
    focus = (s->generic.parent->cursor == s->generic.menuPosition);

    if (s->generic.flags & QMF_GRAYED)
        color = text_color_disabled;
    else if (focus) {
        color = text_color_highlight;
        style |= UI_PULSE;
    } else if (s->generic.flags & QMF_BLINK) {
        color = text_color_highlight;
        style |= UI_BLINK;
    } else
        color = text_color_normal;

    if (focus) {
        // draw cursor
        UI_FillRect(s->generic.left, s->generic.top, s->generic.right - s->generic.left + 1, s->generic.bottom - s->generic.top + 1, listbar_color);
        UI_DrawChar(x, y, 13, UI_CENTER | UI_BLINK | UI_SMALLFONT, color);
    }

    UI_DrawString(x - SMALLCHAR_WIDTH, y, s->generic.name, style | UI_RIGHT, color);
    UI_DrawString(x + SMALLCHAR_WIDTH, y, s->itemnames[s->curvalue], style | UI_LEFT, color);
}

/*
=================
ScrollList_Init
=================
 */
static void ScrollList_Init(menulist_s *l) {
    int w;

    l->oldvalue = 0;
    l->curvalue = 0;
    l->top = 0;

    if (!l->columns) {
        l->columns = 1;
        l->seperation = 0;
    } else if (!l->seperation) {
        l->seperation = 3;
    }

    w = ((l->width + l->seperation) * l->columns - l->seperation) * SMALLCHAR_WIDTH;

    l->generic.left = l->generic.x;
    l->generic.top = l->generic.y;
    l->generic.right = l->generic.x + w;
    l->generic.bottom = l->generic.y + l->height * SMALLCHAR_HEIGHT;

    if (l->generic.flags & QMF_CENTER_JUSTIFY) {
        l->generic.left -= w / 2;
        l->generic.right -= w / 2;
    }
}

/*
=================
ScrollList_Key
=================
 */
sfxHandle_t ScrollList_Key(menulist_s *l, int key) {
    int x;
    int y;
    int w;
    int i;
    int j;
    int c;
    int cursorx;
    int cursory;
    int column;
    int index;

    switch (key) {
        case K_MOUSE1:
            if (l->generic.flags & QMF_HASMOUSEFOCUS) {
                // check scroll region
                x = l->generic.x;
                y = l->generic.y;
                w = ((l->width + l->seperation) * l->columns - l->seperation) * SMALLCHAR_WIDTH;
                if (l->generic.flags & QMF_CENTER_JUSTIFY) {
                    x -= w / 2;
                }
                if (UI_CursorInRect(x, y, w, l->height * SMALLCHAR_HEIGHT)) {
                    cursorx = (uis.cursorx - x) / SMALLCHAR_WIDTH;
                    column = cursorx / (l->width + l->seperation);
                    cursory = (uis.cursory - y) / SMALLCHAR_HEIGHT;
                    index = column * l->height + cursory;
                    if (l->top + index < l->numitems) {
                        l->oldvalue = l->curvalue;
                        l->curvalue = l->top + index;

                        if (l->oldvalue != l->curvalue && l->generic.callback) {
                            l->generic.callback(l, QM_GOTFOCUS);
                            return (menu_move_sound);
                        }
                    }
                }

                // absorbed, silent sound effect
                return (menu_null_sound);
            }
            break;

        case K_KP_HOME:
        case K_HOME:
            l->oldvalue = l->curvalue;
            l->curvalue = 0;
            l->top = 0;

            if (l->oldvalue != l->curvalue && l->generic.callback) {
                l->generic.callback(l, QM_GOTFOCUS);
                return (menu_move_sound);
            }
            return (menu_buzz_sound);

        case K_KP_END:
        case K_END:
            l->oldvalue = l->curvalue;
            l->curvalue = l->numitems - 1;
            if (l->columns > 1) {
                c = (l->curvalue / l->height + 1) * l->height;
                l->top = c - (l->columns * l->height);
            } else {
                l->top = l->curvalue - (l->height - 1);
            }
            if (l->top < 0)
                l->top = 0;

            if (l->oldvalue != l->curvalue && l->generic.callback) {
                l->generic.callback(l, QM_GOTFOCUS);
                return (menu_move_sound);
            }
            return (menu_buzz_sound);

        case K_PGUP:
        case K_KP_PGUP:
            if (l->columns > 1) {
                return menu_null_sound;
            }

            if (l->curvalue > 0) {
                l->oldvalue = l->curvalue;
                l->curvalue -= l->height - 1;
                if (l->curvalue < 0)
                    l->curvalue = 0;
                l->top = l->curvalue;
                if (l->top < 0)
                    l->top = 0;

                if (l->generic.callback)
                    l->generic.callback(l, QM_GOTFOCUS);

                return (menu_move_sound);
            }
            return (menu_buzz_sound);

        case K_PGDN:
        case K_KP_PGDN:
            if (l->columns > 1) {
                return menu_null_sound;
            }

            if (l->curvalue < l->numitems - 1) {
                l->oldvalue = l->curvalue;
                l->curvalue += l->height - 1;
                if (l->curvalue > l->numitems - 1)
                    l->curvalue = l->numitems - 1;
                l->top = l->curvalue - (l->height - 1);
                if (l->top < 0)
                    l->top = 0;

                if (l->generic.callback)
                    l->generic.callback(l, QM_GOTFOCUS);

                return (menu_move_sound);
            }
            return (menu_buzz_sound);

        case K_KP_UPARROW:
        case K_UPARROW:
            if (l->curvalue == 0) {
                return menu_buzz_sound;
            }

            l->oldvalue = l->curvalue;
            l->curvalue--;

            if (l->curvalue < l->top) {
                if (l->columns == 1) {
                    l->top--;
                } else {
                    l->top -= l->height;
                }
            }

            if (l->generic.callback) {
                l->generic.callback(l, QM_GOTFOCUS);
            }

            return (menu_move_sound);

        case K_KP_DOWNARROW:
        case K_DOWNARROW:
            if (l->curvalue == l->numitems - 1) {
                return menu_buzz_sound;
            }

            l->oldvalue = l->curvalue;
            l->curvalue++;

            if (l->curvalue >= l->top + l->columns * l->height) {
                if (l->columns == 1) {
                    l->top++;
                } else {
                    l->top += l->height;
                }
            }

            if (l->generic.callback) {
                l->generic.callback(l, QM_GOTFOCUS);
            }

            return menu_move_sound;

        case K_KP_LEFTARROW:
        case K_LEFTARROW:
            if (l->columns == 1) {
                return menu_null_sound;
            }

            if (l->curvalue < l->height) {
                return menu_buzz_sound;
            }

            l->oldvalue = l->curvalue;
            l->curvalue -= l->height;

            if (l->curvalue < l->top) {
                l->top -= l->height;
            }

            if (l->generic.callback) {
                l->generic.callback(l, QM_GOTFOCUS);
            }

            return menu_move_sound;

        case K_KP_RIGHTARROW:
        case K_RIGHTARROW:
            if (l->columns == 1) {
                return menu_null_sound;
            }

            c = l->curvalue + l->height;

            if (c >= l->numitems) {
                return menu_buzz_sound;
            }

            l->oldvalue = l->curvalue;
            l->curvalue = c;

            if (l->curvalue > l->top + l->columns * l->height - 1) {
                l->top += l->height;
            }

            if (l->generic.callback) {
                l->generic.callback(l, QM_GOTFOCUS);
            }

            return menu_move_sound;
    }

    // cycle look for ascii key inside list items
    if (!Q_isprint(key))
        return (0);

    // force to lower for case insensitive compare
    if (Q_isupper(key)) {
        key -= 'A' - 'a';
    }

    // iterate list items
    for (i = 1; i <= l->numitems; i++) {
        j = (l->curvalue + i) % l->numitems;
        c = l->itemnames[j][0];
        if (Q_isupper(c)) {
            c -= 'A' - 'a';
        }

        if (c == key) {
            // set current item, mimic windows listbox scroll behavior
            if (j < l->top) {
                // behind top most item, set this as new top
                l->top = j;
            } else if (j > l->top + l->height - 1) {
                // past end of list box, do page down
                l->top = (j + 1) - l->height;
            }

            if (l->curvalue != j) {
                l->oldvalue = l->curvalue;
                l->curvalue = j;
                if (l->generic.callback)
                    l->generic.callback(l, QM_GOTFOCUS);
                return ( menu_move_sound);
            }

            return (menu_buzz_sound);
        }
    }

    return (menu_buzz_sound);
}

/*
=================
ScrollList_Draw
=================
 */
void ScrollList_Draw(menulist_s *l) {
    int x;
    int u;
    int y;
    int i;
    int base;
    int column;
    float* color;
    qboolean hasfocus;
    int style;

    hasfocus = (l->generic.parent->cursor == l->generic.menuPosition);

    x = l->generic.x;
    for (column = 0; column < l->columns; column++) {
        y = l->generic.y;
        base = l->top + column * l->height;
        for (i = base; i < base + l->height; i++) {
            if (i >= l->numitems)
                break;

            if (i == l->curvalue) {
                u = x - 2;
                if (l->generic.flags & QMF_CENTER_JUSTIFY) {
                    u -= (l->width * SMALLCHAR_WIDTH) / 2 + 1;
                }

                UI_FillRect(u, y, l->width*SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT + 2, listbar_color);
                color = text_color_highlight;

                if (hasfocus)
                    style = UI_PULSE | UI_LEFT | UI_SMALLFONT;
                else
                    style = UI_LEFT | UI_SMALLFONT;
            } else {
                color = text_color_normal;
                style = UI_LEFT | UI_SMALLFONT;
            }
            if (l->generic.flags & QMF_CENTER_JUSTIFY) {
                style |= UI_CENTER;
            }

            UI_DrawString(
                    x,
                    y,
                    l->itemnames[i],
                    style,
                    color);

            y += SMALLCHAR_HEIGHT;
        }
        x += (l->width + l->seperation) * SMALLCHAR_WIDTH;
    }
}

/*
=================
Menu_AddItem
=================
 */
void Menu_AddItem(menuframework_s *menu, void *item) {
    menucommon_s *itemptr;

    if (menu->nitems >= MAX_MENUITEMS)
        trap_Error("Menu_AddItem: excessive items");

    menu->items[menu->nitems] = item;
    ((menucommon_s*) menu->items[menu->nitems])->parent = menu;
    ((menucommon_s*) menu->items[menu->nitems])->menuPosition = menu->nitems;
    ((menucommon_s*) menu->items[menu->nitems])->flags &= ~QMF_HASMOUSEFOCUS;

    // perform any item specific initializations
    itemptr = (menucommon_s*) item;
    if (!(itemptr->flags & QMF_NODEFAULTINIT)) {
        switch (itemptr->type) {
            case MTYPE_ACTION:
                Action_Init((menuaction_s*) item);
                break;

            case MTYPE_FIELD:
                MenuField_Init((menufield_s*) item);
                break;

            case MTYPE_SPINCONTROL:
                SpinControl_Init((menulist_s*) item);
                break;

            case MTYPE_RADIOBUTTON:
                RadioButton_Init((menuradiobutton_s*) item);
                break;

            case MTYPE_SLIDER:
                Slider_Init((menuslider_s*) item);
                break;

            case MTYPE_BITMAP:
                Bitmap_Init((menubitmap_s*) item);
                break;

            case MTYPE_TEXT:
                Text_Init((menutext_s*) item);
                break;

            case MTYPE_SCROLLLIST:
                ScrollList_Init((menulist_s*) item);
                break;

            case MTYPE_PTEXT:
                PText_Init((menutext_s*) item);
                break;

            case MTYPE_BTEXT:
                BText_Init((menutext_s*) item);
                break;

            default:
                trap_Error(va("Menu_Init: unknown type %d", itemptr->type));
        }
    }

    menu->nitems++;
}

/*
=================
Menu_CursorMoved
=================
 */
void Menu_CursorMoved(menuframework_s *m) {
    void (*callback)(void *self, int notification);

    if (m->cursor_prev == m->cursor)
        return;

    if (m->cursor_prev >= 0 && m->cursor_prev < m->nitems) {
        callback = ((menucommon_s*) (m->items[m->cursor_prev]))->callback;
        if (callback)
            callback(m->items[m->cursor_prev], QM_LOSTFOCUS);
    }

    if (m->cursor >= 0 && m->cursor < m->nitems) {
        callback = ((menucommon_s*) (m->items[m->cursor]))->callback;
        if (callback)
            callback(m->items[m->cursor], QM_GOTFOCUS);
    }
}

/*
=================
Menu_SetCursor
=================
 */
void Menu_SetCursor(menuframework_s *m, int cursor) {
    if (((menucommon_s*) (m->items[cursor]))->flags & (QMF_GRAYED | QMF_INACTIVE)) {
        // cursor can't go there
        return;
    }

    m->cursor_prev = m->cursor;
    m->cursor = cursor;

    Menu_CursorMoved(m);
}

/*
=================
Menu_SetCursorToItem
=================
 */
void Menu_SetCursorToItem(menuframework_s *m, void* ptr) {
    int i;

    for (i = 0; i < m->nitems; i++) {
        if (m->items[i] == ptr) {
            Menu_SetCursor(m, i);
            return;
        }
    }
}

/*
 ** Menu_AdjustCursor
 **
 ** This function takes the given menu, the direction, and attempts
 ** to adjust the menu's cursor so that it's at the next available
 ** slot.
 */
void Menu_AdjustCursor(menuframework_s *m, int dir) {
    menucommon_s *item = NULL;
    qboolean wrapped = qfalse;

wrap:
    while (m->cursor >= 0 && m->cursor < m->nitems) {
        item = (menucommon_s *) m->items[m->cursor];
        if ((item->flags & (QMF_GRAYED | QMF_MOUSEONLY | QMF_INACTIVE))) {
            m->cursor += dir;
        } else {
            break;
        }
    }

    if (dir == 1) {
        if (m->cursor >= m->nitems) {
            if (m->wrapAround) {
                if (wrapped) {
                    m->cursor = m->cursor_prev;
                    return;
                }
                m->cursor = 0;
                wrapped = qtrue;
                goto wrap;
            }
            m->cursor = m->cursor_prev;
        }
    } else {
        if (m->cursor < 0) {
            if (m->wrapAround) {
                if (wrapped) {
                    m->cursor = m->cursor_prev;
                    return;
                }
                m->cursor = m->nitems - 1;
                wrapped = qtrue;
                goto wrap;
            }
            m->cursor = m->cursor_prev;
        }
    }
}

/*
=================
Menu_Draw
=================
 */
void Menu_Draw(menuframework_s *menu) {
    int i;
    menucommon_s *itemptr;

    // draw menu
    for (i = 0; i < menu->nitems; i++) {
        itemptr = (menucommon_s*) menu->items[i];

        if (itemptr->flags & QMF_HIDDEN)
            continue;

        if (itemptr->ownerdraw) {
            // total subclassing, owner draws everything
            itemptr->ownerdraw(itemptr);
        } else {
            switch (itemptr->type) {
                case MTYPE_RADIOBUTTON:
                    RadioButton_Draw((menuradiobutton_s*) itemptr);
                    break;

                case MTYPE_FIELD:
                    MenuField_Draw((menufield_s*) itemptr);
                    break;

                case MTYPE_SLIDER:
                    Slider_Draw((menuslider_s*) itemptr);
                    break;

                case MTYPE_SPINCONTROL:
                    SpinControl_Draw((menulist_s*) itemptr);
                    break;

                case MTYPE_ACTION:
                    Action_Draw((menuaction_s*) itemptr);
                    break;

                case MTYPE_BITMAP:
                    Bitmap_Draw((menubitmap_s*) itemptr);
                    break;

                case MTYPE_TEXT:
                    Text_Draw((menutext_s*) itemptr);
                    break;

                case MTYPE_SCROLLLIST:
                    ScrollList_Draw((menulist_s*) itemptr);
                    break;

                case MTYPE_PTEXT:
                    PText_Draw((menutext_s*) itemptr);
                    break;

                case MTYPE_BTEXT:
                    BText_Draw((menutext_s*) itemptr);
                    break;

                default:
                    trap_Error(va("Menu_Draw: unknown type %d", itemptr->type));
            }
        }
#ifndef NDEBUG
        if (uis.debug) {
            int x;
            int y;
            int w;
            int h;

            if (!(itemptr->flags & QMF_INACTIVE)) {
                x = itemptr->left;
                y = itemptr->top;
                w = itemptr->right - itemptr->left + 1;
                h = itemptr->bottom - itemptr->top + 1;

                if (itemptr->flags & QMF_HASMOUSEFOCUS) {
                    UI_DrawRect(x, y, w, h, colorYellow);
                } else {
                    UI_DrawRect(x, y, w, h, colorWhite);
                }
            }
        }
#endif
    }

    itemptr = Menu_ItemAtCursor(menu);
    if (itemptr && itemptr->statusbar)
        itemptr->statusbar((void *) itemptr);
}

/*
=================
Menu_ItemAtCursor
=================
 */
void *Menu_ItemAtCursor(menuframework_s *m) {
    if (m->cursor < 0 || m->cursor >= m->nitems)
        return NULL;

    return m->items[m->cursor];
}

/*
=================
Menu_ActivateItem
=================
 */
sfxHandle_t Menu_ActivateItem(menuframework_s *s, menucommon_s* item) {
    if (item->callback) {
        item->callback(item, QM_ACTIVATED);
        if (!(item->flags & QMF_SILENT)) {
            return menu_move_sound;
        }
    }

    return 0;
}

/*
=================
Menu_DefaultKey
=================
 */
sfxHandle_t Menu_DefaultKey(menuframework_s *m, int key) {
    sfxHandle_t sound = 0;
    menucommon_s *item;
    int cursor_prev;

    // menu system keys
    switch (key) {
        case K_MOUSE2:
        case K_ESCAPE:
            UI_PopMenu();
            return menu_out_sound;
    }

    if (!m || !m->nitems)
        return 0;

    // route key stimulus to widget
    item = Menu_ItemAtCursor(m);
    if (item && !(item->flags & (QMF_GRAYED | QMF_INACTIVE))) {
        switch (item->type) {
            case MTYPE_SPINCONTROL:
                sound = SpinControl_Key((menulist_s*) item, key);
                break;

            case MTYPE_RADIOBUTTON:
                sound = RadioButton_Key((menuradiobutton_s*) item, key);
                break;

            case MTYPE_SLIDER:
                sound = Slider_Key((menuslider_s*) item, key);
                break;

            case MTYPE_SCROLLLIST:
                sound = ScrollList_Key((menulist_s*) item, key);
                break;

            case MTYPE_FIELD:
                sound = MenuField_Key((menufield_s*) item, &key);
                break;
        }

        if (sound) {
            // key was handled
            return sound;
        }
    }

    // default handling
    switch (key) {
#ifndef NDEBUG
        case K_F11:
            uis.debug ^= 1;
            break;

        case K_F12:
            trap_Cmd_ExecuteText(EXEC_APPEND, "screenshotjpeg\n");
            break;
#endif
        case K_KP_UPARROW:
        case K_UPARROW:
            cursor_prev = m->cursor;
            m->cursor_prev = m->cursor;
            m->cursor--;
            Menu_AdjustCursor(m, -1);
            if (cursor_prev != m->cursor) {
                Menu_CursorMoved(m);
                sound = menu_move_sound;
            }
            break;

        case K_TAB:
        case K_KP_DOWNARROW:
        case K_DOWNARROW:
            cursor_prev = m->cursor;
            m->cursor_prev = m->cursor;
            m->cursor++;
            Menu_AdjustCursor(m, 1);
            if (cursor_prev != m->cursor) {
                Menu_CursorMoved(m);
                sound = menu_move_sound;
            }
            break;

        case K_MOUSE1:
        case K_MOUSE3:
            if (item)
                if ((item->flags & QMF_HASMOUSEFOCUS) && !(item->flags & (QMF_GRAYED | QMF_INACTIVE)))
                    return (Menu_ActivateItem(m, item));
            break;

        case K_JOY1:
        case K_JOY2:
        case K_JOY3:
        case K_JOY4:
        case K_AUX1:
        case K_AUX2:
        case K_AUX3:
        case K_AUX4:
        case K_AUX5:
        case K_AUX6:
        case K_AUX7:
        case K_AUX8:
        case K_AUX9:
        case K_AUX10:
        case K_AUX11:
        case K_AUX12:
        case K_AUX13:
        case K_AUX14:
        case K_AUX15:
        case K_AUX16:
        case K_KP_ENTER:
        case K_ENTER:
            if (item)
                if (!(item->flags & (QMF_MOUSEONLY | QMF_GRAYED | QMF_INACTIVE)))
                    return (Menu_ActivateItem(m, item));
            break;
    }

    return sound;
}

/*
=================
Menu_Cache
=================
 */
void Menu_Cache(void) {
    uis.charset = trap_R_RegisterShaderNoMip("gfx/2d/bigchars");
    uis.charsetProp = trap_R_RegisterShaderNoMip("menu/art/font1_prop.tga");
    uis.charsetPropGlow = trap_R_RegisterShaderNoMip("menu/art/font1_prop_glo.tga");
    uis.charsetPropB = trap_R_RegisterShaderNoMip("menu/art/font2_prop.tga");
    uis.cursor = trap_R_RegisterShaderNoMip("menu/art/3_cursor2");
    uis.rb_on = trap_R_RegisterShaderNoMip("menu/art/switch_on");
    uis.rb_off = trap_R_RegisterShaderNoMip("menu/art/switch_off");

    uis.whiteShader = trap_R_RegisterShaderNoMip("white");
    if (uis.glconfig.hardwareType == GLHW_RAGEPRO) {
        // the blend effect turns to shit with the normal
        uis.menuBackShader = trap_R_RegisterShaderNoMip("menubackRagePro");
    } else {
        uis.menuBackShader = trap_R_RegisterShaderNoMip("menuback");
    }
    uis.menuBackNoLogoShader = trap_R_RegisterShaderNoMip("menubacknologo");

    menu_in_sound = trap_S_RegisterSound("sound/misc/menu1.wav", qfalse);
    menu_move_sound = trap_S_RegisterSound("sound/misc/menu2.wav", qfalse);
    menu_out_sound = trap_S_RegisterSound("sound/misc/menu3.wav", qfalse);
    menu_buzz_sound = trap_S_RegisterSound("sound/misc/menu4.wav", qfalse);
    weaponChangeSound = trap_S_RegisterSound("sound/weapons/change.wav", qfalse);

    // need a nonzero sound, make an empty sound for this
    menu_null_sound = -1;

    sliderBar = trap_R_RegisterShaderNoMip("menu/art/slider2");
    sliderButton_0 = trap_R_RegisterShaderNoMip("menu/art/sliderbutt_0");
    sliderButton_1 = trap_R_RegisterShaderNoMip("menu/art/sliderbutt_1");
}

